﻿// Learn more about F# at http://fsharp.net
namespace ProcessingFS
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media.Imaging
open System.Windows.Media

type DrawModes =
     | Center
     | Corner
     | Corners
     | Radius

type PApp () =

    // mutable variables

    let mutable stroke = Color.FromRgb(0uy,0uy,0uy)
    let mutable background = Color.FromRgb(125uy,125uy,125uy)
    let mutable fill = Color.FromRgb(225uy,225uy,225uy)
    let mutable mouseX = 0
    let mutable mouseY = 0
    let mutable pmouseX = 0
    let mutable pmouseY = 0
    let mutable width = 100
    let mutable height = 100
    let mutable setup = true
    let mutable rectMode = Corner
    let mutable circMode = Center
    let mutable clicked = false
    let mutable noFill = false
    let mutable noStroke = false
    let mutable loop = true
    let mutable redraw = false

    // color parser locals
    
    let parseColorG (grayScale:int) = let by = byte grayScale
                                      Color.FromRgb(by,by,by)
    let parseColorRGB (r:int) (g:int) (b:int) = let r = byte r
                                                let b = byte b
                                                let g = byte g
                                                Color.FromRgb(r,g,b)

    // general setup
    let window = new Window(Title="ProcessingFS",Width=float width + 25.0,Height=float height + 50.0)
    let mutable bitmap = new System.Windows.Media.Imaging.WriteableBitmap(width,height,96.0,96.0,System.Windows.Media.PixelFormats.Bgr32,null)
    let image = new Image(Width=float width,Height=float height)
    let dispatch (func:unit->unit) =
        window.Dispatcher.BeginInvoke(Action(fun _ -> func())) |> ignore
    do
        window.MouseMove.Add(fun args -> let pos = args.GetPosition(image)
                                         dispatch(fun () -> mouseX <- int pos.X
                                                            mouseY <- int pos.Y))
        window.MouseLeftButtonDown.Add(fun _ -> dispatch(fun () -> clicked <- true))
        window.ResizeMode <- ResizeMode.NoResize
        window.Content <- image
        bitmap.Clear(background)
        image.Source <- bitmap
        
    /// Call this to setup and show the processing window
    member this.Show () =
        this.Setup()
        async {
            let lagging = ref false
            let draw () = lagging := true
                          this.Draw()
                          pmouseX <- mouseX
                          pmouseY <- mouseY
                          lagging := false
            while true do
                do! Async.Sleep 16
                if clicked then
                    dispatch this.MousePressed
                    clicked <- false
                if setup then
                    if loop then
                        if not !lagging then
                            dispatch draw
                    elif redraw then
                        if not !lagging then
                            dispatch draw
                            redraw <- false
                        
        } |> Async.StartImmediate
        
        let app = new Application()
        app.Run(window) |> ignore

    // override methods

    abstract member Setup: unit -> unit
    default this.Setup () = setup <- false
    abstract member Draw: unit -> unit
    default this.Draw () = ()
    abstract member MousePressed: unit -> unit
    default this.MousePressed () = ()

    // public accessors

    /// The last recorded mouse X value
    member this.MouseX = mouseX
    /// The last recorded mouse Y value
    member this.MouseY = mouseY
    /// The mouse value before the last recorded mouse X value
    member this.PMouseX = pmouseX
    /// The mouse value before the last recorded mouse Y value
    member this.PMouseY = pmouseY
    /// The current drawable region width
    member this.Width = width
    /// The current drawable region height
    member this.Height = height

    // general control
    
    /// Specifies the size for the current window and clears the screen to the background
    member this.Size x y = width <- x
                           height <- y
                           mouseX <- 0
                           mouseY <- 0
                           bitmap <- new System.Windows.Media.Imaging.WriteableBitmap(width,height,96.0,96.0,System.Windows.Media.PixelFormats.Bgr32,null)
                           bitmap.Clear background
                           window.SetValue(Window.WidthProperty,float width + 25.0)
                           window.SetValue(Window.HeightProperty,float height + 50.0)
                           image.Width <- float width
                           image.Height <- float height
                           image.Source <- bitmap
    /// Causes one frame to be drawn if the window is in noloop mode
    member this.ReDraw() = if not loop then
                               redraw <- true    
    /// Starts the draw loop up if it has been stopped
    member this.Loop() = loop <- true
    /// Stops the drawloop
    member this.NoLoop() = loop <- false
    /// Exits the application
    member this.Exit() = window.Close()



    // color Methods

    /// Sets the background to the specified color and clears the screen
    member this.Background (color:Color) = background <- color
                                           bitmap.Clear background
    /// Sets the background to the specified color and clears the screen
    member this.Background (gray:int) = background <- parseColorG gray
                                        bitmap.Clear background
    /// Sets the background to the specified color and clears the screen
    member this.Background ((r:int),(g:int),(b:int)) = background <- parseColorRGB r g b
                                                       bitmap.Clear background
    /// Sets the stroke to the specified color
    member this.Stroke (color:Color) = stroke <- color
                                       noStroke <- false
    /// Sets the stroke to the specified color
    member this.Stroke (gray:int) = stroke <- parseColorG gray
                                    noStroke <- false
    /// Sets the stroke to the specified color
    member this.Stroke ((r:int),(g:int),(b:int)) = stroke <- parseColorRGB r g b
                                                   noStroke <- false
    /// Sets the fill to the specified color
    member this.Fill (color:Color) = fill <- color
                                     noFill <- false
    /// Sets the fill to the specified color
    member this.Fill (gray:int) = fill <- parseColorG gray
                                  noFill <- false
    /// Sets the fill to the specified color
    member this.Fill ((r:int),(g:int),(b:int)) = fill <- parseColorRGB r g b
                                                 noFill <- false
    /// Removes the fill from shapes
    member this.NoFill () = noFill <- true
    /// Removes the stroke from shapes
    member this.NoStroke () = noStroke <- true

    // draw methods

    /// Draws a point at x y with the stroke as color
    member this.Point x y = bitmap.SetPixel(x,y,stroke)
    /// Draws a line from x1 y1 to x2 y2 with the stroke as color
    member this.Line x1 y1 x2 y2 = bitmap.DrawLine(x1,y1,x2,y2,stroke)
    /// Draws a Rectangle using the rectMode and fill and stroke for color
    member this.Rect x y a b = match rectMode with
                               | Corner -> if not noFill then
                                               bitmap.FillRectangle(x,y,(x+a),(y+b),fill)
                                           if not noStroke then
                                               bitmap.DrawRectangle(x,y,(x+a),(y+b),stroke)
                               | Corners -> if not noFill then
                                                bitmap.FillRectangle(x,y,a,b,fill)
                                            if not noStroke then
                                                bitmap.DrawRectangle(x,y,a,b,stroke)
                               | Center -> let aOver2 = a/2
                                           let bOver2 = b/2
                                           let x1 = x - aOver2
                                           let y1 = y - bOver2
                                           let x2 = x + aOver2
                                           let y2 = y + bOver2
                                           if not noFill then
                                               bitmap.FillRectangle(x1,y1,x2,y2,fill)
                                           if not noStroke then
                                               bitmap.DrawRectangle(x1,y1,x2,y2,fill)
                               | Radius -> let aOver2 = a
                                           let bOver2 = b
                                           let x1 = x - aOver2
                                           let y1 = y - bOver2
                                           let x2 = x + aOver2
                                           let y2 = y + bOver2
                                           if not noFill then
                                               bitmap.FillRectangle(x1,y1,x2,y2,fill)
                                           if not noStroke then
                                               bitmap.DrawRectangle(x1,y1,x2,y2,fill)
    /// Draws an Ellipse using the circMode and fill and stroke for color
    member this.Ellipse x y a b = match circMode with
                                  | Corner -> if not noFill then
                                                  bitmap.FillEllipse(x,y,(x+a),(y+b),fill)
                                              if not noStroke then
                                                  bitmap.DrawEllipse(x,y,(x+a),(y+b),stroke)
                                  | Corners -> if not noFill then
                                                   bitmap.FillEllipse(x,y,a,b,fill)
                                               if not noStroke then
                                                   bitmap.DrawEllipse(x,y,a,b,stroke)
                                  | Center -> let radiusx = a/2
                                              let radiusy = b/2
                                              let x1 = x - radiusx
                                              let y1 = y - radiusy
                                              let x2 = x + radiusx
                                              let y2 = y + radiusy
                                              if not noFill then
                                                  bitmap.FillEllipse(x1,y1,x2,y2,fill)
                                              if not noStroke then
                                                  bitmap.DrawEllipse(x1,y1,x2,y2,stroke)
                                  | Radius -> let radiusx = a
                                              let radiusy = b
                                              if not noFill then
                                                  bitmap.FillEllipseCentered(x,y,a,b,fill)
                                              if not noStroke then
                                                  bitmap.DrawEllipseCentered(x,y,a,b,fill)
    /// Draws a quadrilateral with the corners at each of the points in clockwise or counterClockwise order
    member this.Quad x1 y1 x2 y2 x3 y3 x4 y4 = if not noFill then
                                                   bitmap.FillQuad(x1,y1,x2,y2,x3,y3,x4,y4,fill)
                                               if not noStroke then
                                                   bitmap.DrawQuad(x1,y1,x2,y2,x3,y3,x4,y4,stroke)
    /// Draws a triangle with each of the corners at each of the points in clockwise or counterClockwise order
    member this.Triangle x1 y1 x2 y2 x3 y3 = if not noFill then
                                                 bitmap.FillTriangle(x1,y1,x2,y2,x3,y3,fill)
                                             if not noStroke then
                                                 bitmap.DrawTriangle(x1,y1,x2,y2,x3,y3,stroke)

    // mode setters

    /// Sets the RectMode. 
    /// Center specifies x y for the center point and a b for width and height
    /// Corners specifies x y as top corner and a b for bottom corner
    /// Corner specifies x y as top corner and a b for width and height
    member this.RectMode mode = rectMode <- mode
    /// Sets the CircMode.
    /// Center specifies x y for the center point and a b for width and height
    /// Corners specifies x y as top corner and a b for bottom corner
    /// Corner specifies x y as top corner and a b for width and height
    member this.EllipseMode mode = circMode <- mode

    // math Utils

    /// Returns the distance between the two points as a float
    member this.Dist (x1,y1,x2,y2) = let x = x2 - x1
                                     let y = y2 - y1
                                     Math.Sqrt(float(x*x + y*y))
    /// Returns the distance between the two points as a float
    member this.Dist (x1,y1,x2,y2) = let x = x2 - x1
                                     let y = y2 - y1
                                     Math.Sqrt(x*x + y*y)

 
                                               
